package controleur;

import java.awt.EventQueue;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.atomic.AtomicLong;

import javax.xml.stream.XMLStreamException;

import sim_moteur.Coordonnee;
import sim_moteur.Simulateur;
import sim_moteur.Simulateur.ChosesPossibles;
import sim_moteur.element.Chose;
import sim_moteur.element.Vivant;
import sim_moteur.factory.SimFactory;
import vue.Affichable;
import vue.AppWindow;
import vue.AppWindow.FournisseurDeChose;
import vue.XY;

public class SimulationControllerThread extends Thread implements
		FournisseurDeChose {
	// private int threadSelection;
	// private volatile boolean isfinished;
	private Collection<Affichable> chosesBasiques;
	private Collection<Affichable> vegetaux;
	private Collection<Affichable> animaux;
	private AppWindow vue;

	private Simulateur modele;
	private RendezVous rendezVous;
	private AtomicLong periode;
	private String simPrefix;

	public SimulationControllerThread(AppWindow vue, RendezVous rendezVous,
			long periode, String simPrefix) {
		assert vue != null : "vue nulle";
		assert rendezVous != null : "synchornisateur de pas null";
		assert periode > 0 : "périore nulle";
		assert simPrefix != null : "prefix nul";
		this.vue = vue;
		this.rendezVous = rendezVous;
		this.periode = new AtomicLong(periode);
		setPrefix(simPrefix);

		modele = new Simulateur(simPrefix);
	}

	/**
	 * Configure les paramètres de la simulation. Lecture du fichier de
	 * propriétés et inscription des propriétés dans la simulation.
	 */
	public void configureSimulation() {
		SimFactory fabrique = SimFactory.getInstance(simPrefix);
		modele.ajouteAnimaux(fabrique.getAll(ChosesPossibles.ANIMAL));
		modele.ajouteVegetaux(fabrique.getAll(ChosesPossibles.VEGETAL));
		modele.ajouteChoses(fabrique.getAll(ChosesPossibles.CHOSE));
	}

	public void init() {

		SimFactory.reset(simPrefix);
		modele.init();
		configureSimulation();
		prepareAffichable();

	}

	public void run() {

		long tempCourant = 0;
		long sleepTime = 0;
		while (true) {

			while (/* !isfinished && */modele.resteToujoursUnVivant()) {

				try {
					// On simule
					rendezVous.rejoint();
					modele.step();
					modele.enleveMort();

					// on gère la période de rafraichissement
					tempCourant = System.currentTimeMillis();
					Thread.sleep(Math.max(0, periode.get()
							- (tempCourant - sleepTime)));
					sleepTime = System.currentTimeMillis();

					System.out.println(tempCourant - sleepTime + "  ms");

				} catch (InterruptedException e) {
					System.out.println("interrupted from wait");
					pause();
				}
				// Partie vue. On affiche seulement le modèle
				// sélectionné
				prepareAffichable();
			}
			try {
				modele.enleveMort();
				prepareAffichable();
				// on arrête le tout lorsqu'il n'y a plus de simulateur en
				// fonction
				int nbRestant = rendezVous.retire();
				if (nbRestant == 1)
					EventQueue.invokeLater(() -> {
						vue.stop();
					});
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println(simPrefix + " a terminé\n");

			synchronized (this) {
				// C'est le init qui repart les threads
				try {
					this.wait();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}

	}

	public void prepareAffichable() {
		chosesBasiques = adapteChose(modele.getLesChosesBasiques().values());
		vegetaux = adapteChose(modele.getVegetaux().values());
		animaux = adapteChose(modele.getAnimaux().values());
	}

	// /* package */final void stopSim() {
	// isfinished = true;
	// }

	private void pause() {
		rendezVous.pause();
	}

	private <I extends Chose> Collection<Affichable> adapteChose(
			Collection<I> choses) {
		Collection<Affichable> retVal = new ArrayList<>(choses.size());
		for (I chose : choses) {
			String name = chose.getName();
			String contenu = chose.toString();
			contenu += simPrefix + "\n";
			boolean estVivant = chose.estVivant();
			Coordonnee coordonnee = chose.getCoordonnees();
			XY xy = new XY(coordonnee.getX(), coordonnee.getY());
			float vie = chose instanceof Vivant ? ((Vivant) chose).getVie() : 0;
			retVal.add(new Affichable(name, contenu, estVivant, xy, vie,
					simPrefix));
		}
		return retVal;
	}

	@Override
	public Collection<Affichable> getVegetaux() {
		return vegetaux;
	}

	@Override
	public String getNameForIndex(int index) {
		return modele.getNameForIndex(index);
	}

	@Override
	public Collection<Affichable> getLesChosesBasiques() {
		return chosesBasiques;
	}

	@Override
	public Collection<Affichable> getAnimaux() {
		return animaux;
	}

	@Override
	public Affichable getChose(String name) {
		Affichable retVal = null;
		Chose chose = modele.getChose(name);
		if (chose != null) {

			String contenu = chose.toString() + simPrefix + "\n";
			boolean estVivant = chose instanceof Vivant ? ((Vivant) chose)
					.estVivant() : false;
			Coordonnee coordonnee = chose.getCoordonnees();
			XY xy = new XY(coordonnee.getX(), coordonnee.getY());
			float vie = chose instanceof Vivant ? ((Vivant) chose).getVie()
					: 0f;
			retVal = new Affichable(name, contenu, estVivant, xy, vie,
					simPrefix);
		}
		return retVal;
	}

	public void step() {
		modele.step();
		modele.enleveMort();
		prepareAffichable();
		System.out.println("step " + modele.getStep() + "\n"
				+ modele.toString());
	}

	public void setPeriode(long periode) {
		this.periode.set(periode);
	}

	@Override
	public String getPrefix() {
		return simPrefix;
	}

	public void setPrefix(String inPrefix) {
		simPrefix = inPrefix;
		this.setName(simPrefix);
	}

	public Simulateur getModel() {
		return modele;
	}

}
